/* Copyright (c) 2009-2010, Sean A.O. Harney <sharney3@gmail.com> */

#include "omf.h"

/* prototypes */
void _print_permutation(uint8_t * perm, size_t perm_len);
uint8_t *_get_next_permutation(size_t * perm_len);


//indirectly recursive: stmt_process_repeat,if call it
void stmt_process(ASL_NODE_P stmt)
{
    debug2_printf("Processing Statement: %s\n",
		  stmt_type_to_str(stmt->type));
    switch (stmt->type)
      {
      case STMT_FUZZDATA:
	  stmt_process_fuzzdata(&(stmt->params.stmt_fuzzdata));
	  break;
      case STMT_TARGET:
	  stmt_process_target(&(stmt->params.stmt_target));
	  break;
      case STMT_START:
	  stmt_process_start(stmt->params.stmt_start.start_arg);
	  break;
      case STMT_FLAG:
	  stmt_process_flag(&(stmt->params.stmt_flag));
	  break;
      case STMT_SEND:
	  //TODO: here or in .y perhaps, assert target socket or target command stdin with send/expect
	  stmt_process_send(&(stmt->params.stmt_send));
	  break;
      case STMT_EXPECT:
	  stmt_process_expect(&(stmt->params.stmt_expect));
	  break;
      case STMT_PRINT:
	  stmt_process_print(&(stmt->params.stmt_print));
	  break;
      case STMT_INCLUDE:
	  stmt_process_include(stmt->params.stmt_include.file);
	  break;
      case STMT_SHELL:
	  stmt_process_shell(stmt->params.stmt_include.file);
	  break;
      case STMT_REPEAT:
	  stmt_process_repeat(&(stmt->params.stmt_repeat));
	  break;
      case STMT_IF:
	  stmt_process_if(&(stmt->params.stmt_if));
	  break;
      case STMT_RETURN:
	  stmt_process_return(&(stmt->params.stmt_return));
	  break;
      case STMT_SLEEP:
	  stmt_process_sleep(stmt->params.stmt_sleep.secs);
	  break;
      case STMT_EXPR_STMT:
	  stmt_process_expr_stmt(stmt->params.stmt_expr_stmt.expr);
	  break;
      case STMT_VAR_DECL:
	  stmt_process_var_decl(&(stmt->params.stmt_var_decl));
	  break;
      default:
	  error_println("stmt_process(): unknown stmt type: %d",
			stmt->type);
      }
}


void stmt_process_fuzzdata(struct st_stmt_fuzzdata *stmt_fuzzdata)
{
    glob.vm_state.fuzzdata_state = stmt_fuzzdata;
}

void stmt_process_target(struct st_stmt_target *stmt_target)
{
    glob.vm_state.target_state = stmt_target;
}

//pass start_arg = -1 if none given.
void stmt_process_start(int start_arg)
{
    uint8_t *perm, *stdin_perm;
    size_t perm_len, stdin_perm_len;

//cast to discard volatile 
    struct st_vm_state *vm_state = (struct st_vm_state *) &(glob.vm_state);

    struct st_stmt_fuzzdata *fuzzdata_state = vm_state->fuzzdata_state;
    struct st_stmt_target *target_state = vm_state->target_state;

    int max_proc;
#define DEFAULT_MAX_PROC 10	// for command,dl
#define DEFAULT_MAX_PROC_SOCKET 1	// for socket
    if (start_arg == -1)
      {
	  if (target_state->socket == true)
	      max_proc = DEFAULT_MAX_PROC_SOCKET;
	  else
	      max_proc = DEFAULT_MAX_PROC;
    } else
	max_proc = start_arg;

// first start stmt creates the log
    if (glob.outputlog_active == true && glob.outputlog_dirname == NULL)
      {
	  /* create OMFoutputlog dir and symlink */
	  outputlog_init(glob.outputlog_progname);
      }


    if (debug_level == 0)
      {
	  fprintf(stderr, "Hash mark printing on (%d/hash mark).\n",
		  HASH_MARK_RATIO);
	  fflush(stderr);
      }


    if (fuzzdata_state->pattern != NULL)
	regldg_launch(fuzzdata_state->pattern,
		      fuzzdata_state->pattern_maxlen);
    else if (fuzzdata_state->file != NULL)
	dictfile_launch(fuzzdata_state->file);	//TODO: err check
    else if (fuzzdata_state->random == true)
      {
	  random_init(&(vm_state->random_handle), RANDOM_DEFAULT_SRC);	//TODO: err chck bool
    } else
      {				/* won't get here */
      }


//TODO make an isFunction
    struct st_vm_flag_state *fs = glob.vm_state.flag_state;
    if (fs->memory_size != -1 || fs->memory_rss != -1 || fs->memory_pss != -1)	// some memory size flagging
      {
	  if (memusage_init() == false)	//registers ALRM handler and real itimer
	      fatal_error_println("memusage_init() failed.");
      }
//TODO: disable itimer,signal handler afer start finished

    while (1)
      {
	  perm = _get_next_permutation(&perm_len);
	  if (perm == NULL)
	      break;

	  _print_permutation(perm, perm_len);

	  if (target_state->command == true)
	    {
		//TODO: enforce should only be string fuzzing. cstring,unicode?
		perm[perm_len] = '\0';	//tie off c-string
		if (target_state->command_ind_doubledollar != -1 && target_state->command_stdin == false)	//is there a single $$ arg in target_command args  
		  {

		      LLIST_P doubledollar_arg =
			  llist_get_at_index(target_state->command_argv,
					     target_state->
					     command_ind_doubledollar);
		      if (doubledollar_arg != NULL)
			{
			    doubledollar_arg->element = checked_strdup((char *) perm);	//substitute the single "$$" arg
		      } else
			{	/* shouldn't get here */
			}

		      stdin_perm = NULL;
		      stdin_perm_len = 0;

		} else if (target_state->command_ind_doubledollar == -1
			   && target_state->command_stdin == true)
		  {
		      //stdin fuzzing 
		      stdin_perm = perm;
		      stdin_perm_len = perm_len;
		} else
		  {
		      fatal_error_println("Shouldn't have got here!");
		  }


		command_launch(target_state->command_argv,
			       stdin_perm, stdin_perm_len);
	  } else if (target_state->dl == true)
	    {
		if (target_state->dl_ind_doubledollar != -1)	/* is there a single $$ arg in target dl args ? */
		  {
		      LLIST_P dollar_arg =
			  llist_get_at_index(target_state->dl_args,
					     target_state->
					     dl_ind_doubledollar);
		      if (dollar_arg != NULL)
			{
			    //substitute the single "$$" arg
			    struct st_dlarg *dlarg;
			    dlarg = dollar_arg->element;
			    dlarg->type = TYPE_GENERIC_PTR;	//might be a c string. might be binary
			    dlarg->ptr_len = perm_len;
			    dlarg->val.ptr_val =
				checked_strdup((char *) perm);
		      } else
			{	/* shouldn't get here */
			}
		  }
		//TODO: error check
		dl_launch(target_state->dl_args, target_state->dl_libfname,
			  target_state->dl_symbol);
	  } else if (target_state->socket == true)
	    {
		bool ret = socket_launch(target_state->socket_host,
					 target_state->socket_port,
					 target_state->socket_type, NULL);
		if (ret == false)
		  {

		  }


	  } else
	    {			/* wont get here */
	    }


	  if (num_current_target_processes >= max_proc)
	    {
		debug1_printf
		    ("\nLimit of target processes reached,  1 sec (per `.`)");
		while (1)
		  {

		      //sleep(1);
		      //TODO: SLEEP A SECOND SOMEHOW

		      //debug_unquiet_printf(".");
		      if (num_current_target_processes < max_proc)
			  break;	//gets here very often on first iteration 
		  }
		debug1_printf("\n");
	    }


/* Reason for vm_state->perm_count != 0 is so that the modulus does evaluate to true for 0 and print an additional hash mark */
	  if (debug_level == 0 && vm_state->perm_count != 0 && vm_state->perm_count % HASH_MARK_RATIO == 0)	//no -d or -q flags
	    {
		fprintf(stderr, "#");
	    }

	  vm_state->perm_count++;
	  usleep(5000);
      }				// end while loop


    //while loop ends when get_next_perm() returns NULL,

//processes may still be running however.
    debug_unquiet_printf
	("\n***** COMPLETED start statement! count=%ld*****\n",
	 vm_state->perm_count);

    debug_unquiet_printf("processes: %d, reaped: %d\n",
			 num_current_target_processes,
			 num_reaped_target_processes);
//after each start stmt wait for target child processes to die.
    debug_unquiet_printf
	(" Waiting for %d target child processes to die.\n",
	 num_current_target_processes);


    // while (num_current_target_processes > 0)
    int i;
    for (i = 0; num_current_target_processes > 0 && i < 3; i++)
	sleep(1);

    if (num_current_target_processes == 0)
	debug_unquiet_printf("All target child processes reaped.\n");
    else
	debug_unquiet_printf("processes: %d, reaped: %d\n",
			     num_current_target_processes,
			     num_reaped_target_processes);


    //random_handle not reset
    //TODO: fix so it closes or reuses
    vm_state_reset();		// reset state
}


void stmt_process_flag(struct st_stmt_flag *stmt_flag)
{
    struct st_vm_flag_state *fs = glob.vm_state.flag_state;

    if (stmt_flag->termination_any == true)
	fs->termination_any = true;
    else if (stmt_flag->termination_signal_any == true)
	fs->termination_signal_any = true;
    else if (stmt_flag->termination_exit_any == true)
	fs->termination_exit_any = true;
    else if (stmt_flag->termination_signal)
	llist_append(&(stmt_flag->val), &(fs->termination_signals));
    else if (stmt_flag->termination_exit)
	llist_append(&(stmt_flag->val), &(fs->termination_exits));
    else if (stmt_flag->memory_size == true)
	fs->memory_size = stmt_flag->val;
    else if (stmt_flag->memory_rss == true)
	fs->memory_rss = stmt_flag->val;
    else if (stmt_flag->memory_pss == true)
	fs->memory_pss = stmt_flag->val;



}

void stmt_process_send(struct st_stmt_send *stmt_send)
{
    //TODO: create ASL of sends and expects.
    //TODO: stmt_process_start will deliver ASL via socket.c pipe writer
}

void stmt_process_expect(struct st_stmt_expect *stmt_expect)
{
    //TODO: create ASL of sends and expects.
}

//print msg and a newline to stdout
void stmt_process_print(struct st_stmt_print *stmt_print)
{
    EXPR_P expr;
    EXPR_P result;

    expr = stmt_print->expr;
    result = expr_eval(expr);
    printf("%s\n", leaf_val_to_str(result));
    fflush(stdout);
}

void stmt_process_include(char *file)
{
    //TODO: default inc paths
    FILE *fp = fopen(file, "r");
    char *old_current_filename = glob.parser_current_filename;
    bool old_current_is_include = glob.parser_current_is_include;

    if (fp == NULL)
	fatal_error_perror(file);
    //partial glob reset
    glob.parser_num_errors = 0;
    glob.parser_current_filename = file;
    glob.parser_current_is_include = true;
    /* false because if parseonly wastrue,woulnt have got to process-time */
    if (!parse_init(fp, false))
	exit(EXIT_FAILURE);
    //not a big deal since done parsing current if in process-time anyway . wont be used elsewhere.
    glob.parser_current_filename = old_current_filename;
    glob.parser_current_is_include = old_current_is_include;


}

void stmt_process_shell(char *command_str)
{
    if (system(command_str) < 0)
      {
	  fprintf(stderr, "system() failed: %s: Command: %s\n",
		  strerror(errno), command_str);
      }
}

//if sr->expr == NULL => inf loop
void stmt_process_repeat(struct st_stmt_repeat *stmt_repeat)
{
    int index = 0;
    int n = 0;
    EXPR_P expr = stmt_repeat->expr;
    ASL_NODE_P stmt = stmt_repeat->stmt;	// single stmt
    EXPR_P result;
    if (expr != NULL)
      {
	  result = expr_eval(expr);
	  if (result->type != EXPR_LEAF || result->u.leaf.type != LEAF_INT)
	      fatal_error_println
		  ("Repeat statement needs an expression which evaluates to type INT");

	  n = result->u.leaf.val.int_val;
      }
//if expr == NULL it never leaves this loop until prgm is killed
    while (1)
      {
	  /* if not an infinite loop (NULL passed in for expr) and reached count break */
	  if (expr != NULL)
	    {
		debug2_printf("repeat stmt: [index=%d < n=%d]\n", index,
			      n);
		if (index >= n)	//== n would have worked
		    break;
	  } else
	      debug2_printf("repeat stmt: [index=%d < inf]\n", index);

	  stmt_process(stmt);	/* indirect recursion back to stmt_process */
	  index++;
      }
}

void stmt_process_if(struct st_stmt_if *stmt_if)
{

    EXPR_P expr = stmt_if->expr;


    EXPR_P result = expr_eval(expr);
    if (result == NULL)
	fatal_error_println("Result IS NULL.expected bool leaf.");	//shouldn't get here

    if (result->type != EXPR_LEAF || result->u.leaf.type != LEAF_BOOL)	//assert bool leaf
	fatal_error_println("Expected bool leaf. 2");	//shouldn't get here

    if (result->u.leaf.val.bool_val == true)	// if expr true => process stmt
      {
	  ASL_NODE_P stmt = stmt_if->stmt;	// single stmt
	  stmt_process(stmt);
      }
}

void stmt_process_return(struct st_stmt_return *stmt_return)
{
    EXPR_P ret_expr = stmt_return->expr;
    EXPR_P result = expr_eval(ret_expr);	//since global namespace, makes no difference if do this here or in calling function expr eval?
    glob.current_function_return = result;
}

void stmt_process_sleep(int secs)
{
    debug1_printf("Sleeping %d seconds.\n", secs);
    sleep(secs);
    debug1_printf("Finished sleeping.\n");
}

void stmt_process_expr_stmt(EXPR_P expr)
{
    EXPR_P result = expr_eval(expr);
    if (result == NULL || result->type != EXPR_LEAF)
      {
	  //error
	  return;
      }
}

void stmt_process_var_decl(struct st_stmt_var_decl *stmt_var_decl)
{
    char *symname;
    if (stmt_var_decl->initialized == false)
      {
	  symname = stmt_var_decl->u.uninitialized.varname;

//TODO: error chk
	  SYMTBL_P symbol_tbl = symbol_tbl_local_stack_get_top();
	  if (symbol_tbl == NULL)
	      fatal_error_println("symbol tbl is NULL?");	// shouldn't happen

	  symbol_tbl_new_entry_variable_uninitialized(symbol_tbl,
						      symname,
						      stmt_var_decl->
						      filename);

    } else			/* initialized */
      {
	  EXPR_P assignment_expr =
	      stmt_var_decl->u.initialized.assignment_expr;
	  EXPR_P lhs = assignment_expr->lhs;
	  EXPR_P rhs = assignment_expr->rhs;

	  if (lhs == NULL)
	      fatal_error_println("process var decl statement. assignment lhs NULL");	//shouldn't ever get here

	  if (lhs->type != EXPR_LEAF)
	      fatal_error_println("Unexpected assignment lhs  not type EXPR_LEAF");	//shouldn't ever get here

	  if (lhs->u.leaf.type != LEAF_ID)	//cant be a literal . cant be a LEAF_DOLLARID_,must be i
	      fatal_error_println("Unexpected assignment lhs leaf is not type LEAF_ID");	//shouldn't ever get here

	  symname = lhs->u.leaf.val.id_val;	//id_val is a symname
	  //TODO: assert variable andnot func

	  if (rhs == NULL)
	      fatal_error_println("assignment rhs NULL");	//shouldn't ever get here

	  EXPR_P result = expr_eval(rhs);
//NOTE: following func asserts result isa leaf..it always will be:w
	  if (result == NULL)
	      error_println("result is NULL?");	// ???
	  else
	    {
		SYMTBL_P symbol_tbl = symbol_tbl_local_stack_get_top();
		if (symbol_tbl == NULL)
		    fatal_error_println("symbol tbl is NULL?");	// shouldn't happen

		symbol_tbl_new_entry_variable_from_leafexpr(symbol_tbl,
							    symname,
							    stmt_var_decl->
							    filename,
							    result);
	    }
	  //TODO err chk, maybe put in local var tbl.
      }
}

void _print_permutation(uint8_t * perm, size_t perm_len)
{
    int i, c;
    float ratio_printable;

    if (debug_level < 1)
	return;

    for (i = 0, c = 0; i < perm_len; i++)
	if (isalnum(perm[i]))
	    c++;

    if (perm_len != 0)
	ratio_printable = ((float) c / (float) perm_len);
    else
	ratio_printable = 0.0;


    debug1_printf("Permutation #%ld : length[%d] : [ ",
		  glob.vm_state.perm_count + 1, perm_len);

    if (perm_len > 10 && ratio_printable < 0.75)
      {
	  debug1_printf("\n");
	  for (i = 0; i < perm_len; i++)
	      debug1_printf("%02X%c%c", perm[i], (i % 2 ? '\0' : ' '),
			    (i % 10 ? '\0' : '\n'));
	  debug1_printf("\n");

    } else
      {
	  for (i = 0; i < perm_len; i++)
	      if (isprint(perm[i]))
		  debug1_printf("%c", perm[i]);
	      else
		  debug1_printf("[0x%02X]", perm[i]);
      }
    debug1_printf(" ]\n");
}

//returns NULL if no more perm
uint8_t *_get_next_permutation(size_t * perm_len)
{
    uint8_t *perm = NULL;
    static char buf[32000];

//cast to discard volatile
    struct st_vm_state *vm_state = (struct st_vm_state *) &(glob.vm_state);

    struct st_stmt_fuzzdata *fuzzdata_state = vm_state->fuzzdata_state;

    if (fuzzdata_state == NULL)	/* shouldnt happen */
	return NULL;

    if (fuzzdata_state->pattern != NULL)
      {
	  perm = regldg_next_permutation(perm_len);
    } else if (fuzzdata_state->file != NULL)
      {
	  perm = (uint8_t *) dictfile_next_permutation();
	  if (perm != NULL)
	      *perm_len = strlen((char *) perm);
    } else if (fuzzdata_state->random == true)
      {
	  perm = (uint8_t *) buf;
	  //TODO: move following to random.c
	  random_get_random_amount_of_bytes(&(vm_state->random_handle),
					    perm, perm_len,
					    fuzzdata_state->
					    random_length_lb,
					    fuzzdata_state->
					    random_length_ub);
      }
    return perm;
}
